001    /*
002     * Copyright 2006 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.transit;
020    
021    import java.net.URI;
022    import java.net.URL;
023    import java.net.URLConnection;
024    import java.io.InputStream;
025    import java.io.OutputStream;
026    import java.io.IOException;
027    import java.io.Writer;
028    import java.io.OutputStreamWriter;
029    
030    import javax.xml.parsers.DocumentBuilder;
031    import javax.xml.parsers.DocumentBuilderFactory;
032    
033    import net.dpml.util.ElementHelper;
034    import net.dpml.transit.info.TransitDirective;
035    import net.dpml.transit.info.CacheDirective;
036    import net.dpml.transit.info.HostDirective;
037    import net.dpml.transit.info.ProxyDirective;
038    import net.dpml.transit.info.LayoutDirective;
039    
040    import net.dpml.lang.ValueDirective;
041    import net.dpml.util.DecodingException;
042    import net.dpml.util.Logger;
043    
044    import org.xml.sax.ErrorHandler;
045    
046    import org.w3c.dom.Element;
047    import org.w3c.dom.Document;
048    
049    /**
050     * Utility class supporting the reading of Transit XML configurations.
051     *
052     * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a>
053     * @version 1.0.0
054     */
055    public class TransitBuilder
056    {
057        private static final String XML_HEADER = 
058          "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
059    
060        private static final String NAME = "transit";
061    
062        private static final String PUBLIC_ID = 
063          "-//DPML//DTD Transit Configuration Version 1.0//EN";
064          
065        private static final String SYSTEM_ID = 
066          "http://download.dpml.net/dtds/transit_1_0.dtd";
067    
068        private static final String RESOURCE = 
069          "net/dpml/transit/transit_1_0.dtd";
070    
071        private static final String DOCTYPE = 
072          "\n<!DOCTYPE "
073          + NAME
074          + " PUBLIC \"" 
075          + PUBLIC_ID
076          + "\" \""
077          + SYSTEM_ID 
078          + "\" >";
079        
080        private static final DTD[] DTDS = new DTD[]
081        {
082            new DTD( 
083              PUBLIC_ID, 
084              SYSTEM_ID, 
085              RESOURCE, null )
086        };
087    
088        private static final DTDResolver DTD_RESOLVER =
089            new DTDResolver( DTDS, TransitBuilder.class.getClassLoader() );
090    
091        private Logger m_logger;
092        
093       /**
094        * Creation of a new transit configuration builder.
095        * @param logger the assigned logging channel
096        */
097        public TransitBuilder( Logger logger )
098        {
099            m_logger = logger;
100        }
101    
102       /**
103        * Construct a transit configuration from a supplied uri.
104        * @param url the configuration url
105        * @return the transit configuration
106        * @exception Exception if an error occurs during configuration loading
107        */
108        public TransitDirective load( final URL url ) throws Exception
109        {
110            URLConnection connection = url.openConnection();
111            InputStream input = connection.getInputStream();
112    
113            final DocumentBuilderFactory factory =
114              DocumentBuilderFactory.newInstance();
115            factory.setValidating( true );
116            factory.setNamespaceAware( true );
117            factory.setExpandEntityReferences( true );
118            DocumentBuilder builder = factory.newDocumentBuilder();
119            builder.setEntityResolver( DTD_RESOLVER );
120            ErrorHandler errors = new SaxMonitor( m_logger );
121            builder.setErrorHandler( errors );
122            
123            final Document document = builder.parse( input );
124            final Element root = document.getDocumentElement();
125            return build( root );
126        }
127    
128       /**
129        * Write a transit directive to an output stream as XML.
130        * @param directive the directive to externalize
131        * @param output the output stream to write to
132        * @exception IOException if an I/O error occurs
133        */
134        public void write( TransitDirective directive, OutputStream output ) throws IOException 
135        {
136            final Writer writer = new OutputStreamWriter( output );
137            try
138            {
139                writer.write( XML_HEADER );
140                writer.write( DOCTYPE );
141                
142                CacheDirective cache = directive.getCacheDirective();
143                String cachePath = cache.getCache();
144                String cacheLayout = cache.getCacheLayout();
145                writeHeader( writer, cachePath, cacheLayout );
146                
147                ProxyDirective proxy = directive.getProxyDirective();
148                writeProxy( writer, proxy );
149                
150                String localPath = cache.getLocal();
151                String localLayout = cache.getLocalLayout();
152                writeLocal( writer, localPath, localLayout );
153                
154                HostDirective[] hosts = cache.getHostDirectives();
155                writeHosts( writer, hosts );
156                
157                writeFooter( writer );
158                writer.write( "\n" );
159            }
160            finally
161            {
162                writer.flush();
163                writer.close();
164            }
165        }
166        
167        //-------------------------------------------------------------
168        // internals supporting XML to directive transformation
169        //-------------------------------------------------------------
170        
171        private TransitDirective build( Element root ) throws Exception
172        {
173            String name = root.getTagName();
174            if( !NAME.equals( name ) )
175            {
176                final String error = 
177                  "Invalid root element name ["
178                  + name
179                  + "].";
180                throw new IOException( error );
181            }
182            
183            String cachePath = ElementHelper.getAttribute( root, "cache" );
184            String cacheLayout = ElementHelper.getAttribute( root, "layout" );
185            
186            Element localElement = ElementHelper.getChild( root, "local" );
187            String localPath = ElementHelper.getAttribute( localElement, "path" );
188            String localLayout = ElementHelper.getAttribute( localElement, "layout" );
189            
190            Element proxyElement = ElementHelper.getChild( root, "proxy" );
191            ProxyDirective proxy = buildProxyDirective( proxyElement );
192            
193            Element hostsElement = ElementHelper.getChild( root, "hosts" );
194            HostDirective[] hosts = buildHosts( hostsElement );
195            
196            Element layoutsElement = ElementHelper.getChild( root, "layouts" );
197            LayoutDirective[] layouts = buildLayouts( layoutsElement );
198            
199            // handlers TBD
200            
201            CacheDirective cache = 
202              new CacheDirective( 
203                cachePath, cacheLayout, localPath, localLayout,
204                CacheDirective.EMPTY_LAYOUTS, hosts );
205            return new TransitDirective( proxy, cache );
206        }
207        
208        private LayoutDirective[] buildLayouts( Element element ) throws Exception
209        {
210            if( null == element )
211            {
212                return null;
213            }
214            else
215            {
216                Element[] layoutElements = ElementHelper.getChildren( element, "layout" );
217                LayoutDirective[] layouts = new LayoutDirective[ layoutElements.length ];
218                for( int i=0; i<layoutElements.length; i++ )
219                {
220                    Element elem = layoutElements[i];
221                    layouts[i] = buildLayout( elem );
222                }
223                return layouts;
224            }
225        }
226                
227        private LayoutDirective buildLayout( Element element ) throws Exception
228        {
229            String id = ElementHelper.getAttribute( element, "id" );
230            String title = ElementHelper.getAttribute( element, "title" );
231            Element codebase = ElementHelper.getChild( element, "codebase" );
232            URI uri = decodeURI( codebase );
233            ValueDirective[] values = getValueDirectives( codebase );
234            return new LayoutDirective( id, title, uri, values );
235        }
236        
237        private URI decodeURI( Element element ) throws DecodingException
238        {
239            String uri = ElementHelper.getAttribute( element, "uri" );
240            if( null == uri )
241            {
242                final String error = "Missing uri attribute.";
243                throw new DecodingException( element, error );
244            }
245            else
246            {
247                try
248                {
249                    return new URI( uri );
250                }
251                catch( Exception e )
252                {
253                    final String error = "Bad uri argument [" + uri + "].";
254                    throw new DecodingException( element, error );
255                    
256                }
257            }
258        }
259        
260        private ValueDirective[] getValueDirectives( Element element )
261        {
262            if( null == element )
263            {
264                return null;
265            }
266            else
267            {
268                Element[] valueElements = ElementHelper.getChildren( element, "value" );
269                ValueDirective[] values = new ValueDirective[ valueElements.length ];
270                for( int i=0; i<valueElements.length; i++ )
271                {
272                    Element elem = valueElements[i];
273                    values[i] = buildValue( elem );
274                }
275                return values;
276            }
277        }
278        
279        private ValueDirective buildValue( Element element )
280        {
281            String target = ElementHelper.getAttribute( element, "target" );
282            String method = ElementHelper.getAttribute( element, "method" );
283            String value = ElementHelper.getAttribute( element, "value" );
284            if( value != null )
285            {
286                return new ValueDirective( target, method, value );
287            }
288            else
289            {
290                ValueDirective[] values = getValueDirectives( element );
291                return new ValueDirective( target, method, values );
292            }
293        }
294        
295        private ProxyDirective buildProxyDirective( Element element )
296        {
297            if( null == element )
298            {
299                return null;
300            }
301            else
302            {
303                String host = ElementHelper.getAttribute( element, "host" );
304                Element credentialsElement = ElementHelper.getChild( element, "credentials" );
305                String username = getUsername( credentialsElement );
306                char[] password = getPassword( credentialsElement );
307                String[] excludes = buildProxyExcludes( element );
308                return new ProxyDirective( host, excludes, username, password );
309            }
310        }
311        
312        private String[] buildProxyExcludes( Element element )
313        {
314            if( null == element )
315            {
316                return null;
317            }
318            else
319            {
320                Element[] elements = ElementHelper.getChildren( element, "exclude" );
321                String[] excludes = new String[ elements.length ];
322                for( int i=0; i<excludes.length; i++ )
323                {
324                    Element elem = elements[i];
325                    excludes[i] = ElementHelper.getValue( elem );
326                }
327                return excludes;
328            }
329        }
330        
331        private HostDirective[] buildHosts( Element element )
332        {
333            Element[] elements = ElementHelper.getChildren( element, "host" );
334            HostDirective[] hosts = new HostDirective[ elements.length ];
335            for( int i=0; i<hosts.length; i++ )
336            {
337                Element elem = elements[i];
338                String id = ElementHelper.getAttribute( elem, "id" );
339                int priority = Integer.parseInt( ElementHelper.getAttribute( elem, "priority" ) );
340                String url = ElementHelper.getAttribute( elem, "url" );
341                String layout = ElementHelper.getAttribute( elem, "layout" );
342                boolean enabled = ElementHelper.getBooleanAttribute( elem, "enabled" );
343                boolean trusted = ElementHelper.getBooleanAttribute( elem, "trusted" );
344                String index = ElementHelper.getAttribute( elem, "index" );
345                String scheme = ElementHelper.getAttribute( elem, "scheme" );
346                String prompt = ElementHelper.getAttribute( elem, "prompt" );
347                Element credentialsElement = ElementHelper.getChild( elem, "credentials" );
348                String username = getUsername( credentialsElement );
349                char[] password = getPassword( credentialsElement );
350                hosts[i] = 
351                  new HostDirective( 
352                    id, priority, url, index, username, password, enabled, trusted,
353                    layout, scheme, prompt );
354            }
355            return hosts;
356        }
357        
358        private String getUsername( Element element )
359        {
360            if( null == element )
361            {
362                return null;
363            }
364            else
365            {
366                return ElementHelper.getAttribute( element, "username" );
367            }
368        }
369        
370        private char[] getPassword( Element element )
371        {
372            if( null == element )
373            {
374                return null;
375            }
376            else
377            {
378                String password = ElementHelper.getAttribute( element, "password" );
379                if( null == password )
380                {
381                    return null;
382                }
383                else
384                {
385                    return password.toCharArray();
386                }
387            }
388        }
389        
390        //-------------------------------------------------------------
391        // internals supporting directive to XML transformation
392        //-------------------------------------------------------------
393        
394        private void writeHeader( Writer writer, String cache, String layout ) throws IOException
395        {
396            writer.write( "\n\n<" + NAME + " cache=\"" + cache + "\" layout=\"" + layout + "\">" );
397        }
398        
399        private void writeFooter( Writer writer ) throws IOException
400        {
401            writer.write( "\n</" + NAME + ">" );
402        }
403    
404        private void writeProxy( Writer writer, ProxyDirective proxy ) throws IOException 
405        {
406            if( null != proxy )
407            {
408                String host = proxy.getHost();
409                String username = proxy.getUsername();
410                String password = getPassword( proxy.getPassword() );
411                String[] excludes = proxy.getExcludes();
412                
413                boolean credentials = ( ( null != username ) || ( null != password ) );
414                
415                if( excludes.length == 0 && ( !credentials ) )
416                {
417                    writer.write( 
418                      "\n  <proxy host=\"" + host + "\"/>" );
419                }
420                else
421                {
422                    writer.write( "\n  <proxy host=\"" + host + "\">" );
423                    if( credentials )
424                    {
425                        writer.write( "\n    <credentials" );
426                        if( null != username )
427                        {
428                            writer.write( " username=\"" + username + "\"" );
429                        }
430                        if( null != password )
431                        {
432                            writer.write( " password=\"" + password + "\"" );
433                        }
434                        writer.write( "/>" );
435                    }
436                    if( excludes.length > 0 )
437                    {
438                        writer.write( "\n    <excludes>" );
439                        for( int i=0; i<excludes.length; i++ )
440                        {
441                            String exclude = excludes[i];
442                            writer.write( "\n      <exclude>" + exclude + "</exclude>" );
443                        }
444                        writer.write( "\n    </excludes>" );
445                    }
446                    
447                    writer.write( "\n  </proxy>" );
448                }
449            }
450        }
451        
452        private void writeLocal( Writer writer, String path, String layout ) throws IOException 
453        {
454            writer.write( "\n  <local path=\"" + path + "\" layout=\"" + layout + "\"/>" );
455        }
456        
457        private void writeHosts( Writer writer, HostDirective[] hosts ) throws IOException 
458        {
459            writer.write( "\n  <hosts>" );
460            for( int i=0; i<hosts.length; i++ )
461            {
462                HostDirective host = hosts[i];
463                writeHost( writer, host );
464            }
465            writer.write( "\n  </hosts>" );
466        }
467        
468        private void writeHost( Writer writer, HostDirective host ) throws IOException 
469        {
470            String id = host.getID();
471            int priority = host.getPriority();
472            String url = host.getHost();
473            boolean enabled = host.getEnabled();
474            boolean trusted = host.getTrusted();
475            String layout = host.getLayout();
476            String index = host.getIndex();
477            String scheme = host.getScheme();
478            String prompt = host.getPrompt();
479            String username = host.getUsername();
480            String password = getPassword( host.getPassword() );
481            boolean credentials = ( ( null != username ) || ( null != password ) );
482            
483            writer.write( "\n    <host id=\"" + id + "\" priority=\"" + priority + "\" url=\"" + url + "\"" );
484            if( !enabled )
485            {
486                writer.write( " enabled=\"false\"" );
487            }
488            if( trusted )
489            {
490                writer.write( " trusted=\"true\"" );
491            }
492            if( null != layout )
493            {
494                writer.write( " layout=\"" + layout + "\"" );
495            }
496            if( null != index )
497            {
498                writer.write( " index=\"" + index + "\"" );
499            }
500            if( ( null != scheme ) && !scheme.equals( "" ) )
501            {
502                writer.write( " scheme=\"" + scheme + "\"" );
503            }
504            if( ( null != prompt ) && !prompt.equals( "" ) )
505            {
506                writer.write( " prompt=\"" + prompt + "\"" );
507            }
508            if( credentials )
509            {
510                writer.write( "\n    <credentials" );
511                if( null != username )
512                {
513                    writer.write( " username=\"" + username + "\"" );
514                }
515                if( null != password )
516                {
517                    writer.write( " password=\"" + password + "\"" );
518                }
519                writer.write( "/>" );
520                writer.write( "\n    </host>" );
521            }
522            else
523            {
524                writer.write( "/>" );
525            }
526        }
527        
528        private String getPassword( char[] password )
529        {
530            if( null == password )
531            {
532                return null;
533            }
534            else
535            {
536                return new String( password );
537            }
538        }
539    }